﻿using log4net;
using Microsoft.Office.Interop.Visio;
using System.Collections.Generic;
using System.Text;

namespace VisioERLeveller.Model {

    public abstract class GraphItem {

        public GraphItem(Graph graph, string id, string name, string text) {
            this.Graph = graph;
            this.ID = id;
            this.Name = name;
            this.Text = text;
        }
                
        public string ID { get; protected set; }

        public Graph Graph { get; protected set; }

        public string Text { get; protected set; }

        public string Name { get; protected set; }

        protected object _Tag { get; private set; }

        public T Tag<T>() {
            return (T)this._Tag;
        }

        public void Tag<T>(T value) {
            this._Tag = value;
        }

    }

    public class Graph {

        private static readonly ILog LOG = LogManager.GetLogger(typeof(Graph));

        public Graph() { }

        readonly Dictionary<string, Link> links = new();
        readonly Dictionary<string, Node> nodes = new();
        readonly Dictionary<string, List<Node>> nameIndexOfNodes = new();

        public int NodeCount => this.nodes.Count;

        public IEnumerable<Link> Links => this.links.Values;

        public IEnumerable<Node> Nodes => this.nodes.Values;

        public Node GetNode(string id) => this.nodes[id];

        public Link GetLink(string id) => this.links[id];

        public IEnumerable<Node> GetNodesByName(string name) => this.nameIndexOfNodes[name];

        public void Add(Link link) {
            var from = this.GetNode(link.FromId);
            var to = this.GetNode(link.ToId);
            from.AddLink(link);
            to.AddLink(link);
            this.links.Add(link.ID, link);
        }

        public void Add(Node node) {
            this.nodes.Add(node.ID, node);
            if (!this.nameIndexOfNodes.ContainsKey(node.Name))
                this.nameIndexOfNodes.Add(node.Name, new List<Node>());
            var list = this.nameIndexOfNodes[node.Name];
            list.Add(node);
        }

        public void Remove(Node node) {
            if (!this.nodes.ContainsKey(node.ID)) {
                LOG.Warn($"Node id={node.ID} not exists in graph");
                return;
            }

            if (this.nameIndexOfNodes.ContainsKey(node.Name))
                this.nameIndexOfNodes.Remove(node.Name);

            this.nodes.Remove(node.ID);
        }

        public void Remove(Link link) {
            if (!this.links.ContainsKey(link.ID)) {
                LOG.Warn($"Link id={link.ID} not exists in graph");
                return;
            }

            link.Remove();
            this.links.Remove(link.ID);
        }

        public void Reduce(Node node) {
            var toRemove = new List<Link>();
            foreach (string linkId in node.Links) {
                if (!this.links.ContainsKey(linkId)) {
                    LOG.Warn($"Link id={linkId} not found in graph, refered by Node id={node.ID}");
                    continue;
                }

                Link link = this.links[linkId];
                toRemove.Add(link);
            }

            foreach (var link in toRemove) {
                this.Remove(link);
            }
            this.Remove(node);
        }

        public Graph Copy() {
            Graph copy = new();
            foreach (Node node in this.nodes.Values) {
                copy.Add(node.CopyTo(copy));
            }
            foreach (Link link in this.links.Values) {
                copy.Add(link.CopyTo(copy));
            }
            return copy;
        }

        public string ToTextString() {
            StringBuilder builder = new();
            builder.AppendLine("Graph:");
            foreach (var node in this.nodes.Values) {
                builder.AppendLine($"\t{node}");
            }
            foreach (var link in this.links.Values) {
                builder.AppendLine($"\t{link}");
            }
            return builder.ToString();
        }
    }

}
